package com.ucode.tool.util;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import cn.hutool.core.util.StrUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

/**
 * JwtToken工具类
 * JWT token的格式：header.payload.signature
 * header的格式（算法、token的类型）：
 * {"alg": "HS512","typ": "JWT"}
 * payload的格式（用户名、创建时间、生成时间）：
 * {"sub":"wang","created":1489079981393,"exp":1489684781}
 * signature的生成算法：
 * HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)
 * 
 * @author: liliang
 * @date: 2019年12月20日 下午3:53:40
 */
public class JwtTokenUtils {
    /**
     * 用户Id
     */
    private static final String USERID = Claims.ID;
    /**
     * 用户名
     */
    private static final String USERNAME = Claims.SUBJECT;
    /**
     * 创建时间
     */
    private static final String CREATED = "created";
    
    /**
     * token请求头名称
     */
    public static final String TOKEN_HEADER = "Authorization";
    /**
     * 默认有效期12小时
     */
    private static final long DEFAULT_EXPIRE_TIME = 12 * 60 * 60;
    /**
     * 默认密钥
     */
    private static final String DEFAULT_SECRET = "WelcomeToFollowUcode";

    /**
     * 生成token
     * @author liliang
     * @date 2019年12月21日
     * @param userId
     * @param username
     * @param expiration 过期时长,单位秒(不设置则用默认12个小时)
     * @param secret 秘钥,不设置则使用默认
     * @return
     */
    public static String generateToken(String userId,String username,Long expiration,String secret) {
        return generateToken(username,null,expiration,secret);
    }
    
    /**
     * 生成token
     * @author liliang
     * @date 2019年12月21日
     * @param userId
     * @param username
     * @param expiration 过期时长,单位秒(不设置则用默认12个小时)
     * @param secret 秘钥,不设置则使用默认
     * @return
     */
    public static String generateToken(String userId,String username,Map<String, Object> claims,Long expiration,String secret) {
        Map<String, Object> _claims = new HashMap<>();
        _claims.put(USERNAME, username);
        _claims.put(USERID, userId);
        _claims.put(CREATED, new Date());
        if(claims != null)
            _claims.putAll(claims);
        return generateToken(_claims,expiration,secret);
    }
    
    /**
     * 根据数据声明生成token令牌
     * @author liliang
     * @date 2019年12月20日
     * @param claims 数据声明
     * @param expiration 过期时长,单位秒(不设置则用默认12个小时)
     * @param secret 秘钥,不设置则使用默认
     * @return token 令牌
     */
    private static String generateToken(Map<String, Object> claims,Long expiration,String secret) {
        secret = StrUtil.isNotBlank(secret) ? secret :  DEFAULT_SECRET;
        return Jwts.builder()
                .setClaims(claims)
                .setExpiration(generateExpirationDate(expiration))
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }
    
    /**
     * 生成token的过期时间
     * @author liliang
     * @date 2019年12月20日
     * @param expiration 过期时长,单位秒(不设置则用默认12个小时)
     * @return
     */
    private static Date generateExpirationDate(Long expiration) {
        expiration = expiration !=null && expiration > 0 ? expiration :DEFAULT_EXPIRE_TIME;
        return new Date(System.currentTimeMillis() + expiration * 1000);
    }
    
    /**
     * 判断token是否已过期
     * @author liliang
     * @date 2019年12月20日
     * @param token 令牌
     * @param secret 秘钥,不设置则使用默认
     * @return false 未过期 true已过期
     */
    public static boolean isTokenExpired(String token,String secret) {
        Date expiredDate = getExpiredDateFromToken(token,secret);
        return expiredDate !=null ? expiredDate.before(new Date()) : true;
    }
    
    /**
     * 验证token令牌是否还有效
     * @author liliang
     * @date 2019年12月21日
     * @param token 客户端传入的token
     * @param username
     * @param secret 秘钥,不设置则使用默认
     * @return
     */
    public static boolean validateToken(String token,  String username,String secret) {
        String userName = getUsernameFromToken(token,secret);
        return userName.equals(username) && !isTokenExpired(token,secret);
    }
    
    /**
     * 判断令牌是否可以刷新
     * @author liliang
     * @date 2019年12月20日
     * @param token 令牌
     * @param secret 秘钥,不设置则使用默认
     * @return true 可刷新 false 不可刷新
     */
    public static boolean canRefresh(String token,String secret) {
        return !isTokenExpired(token,secret);
    }
    
    /**
     * 从令牌获取用户名
     * @author liliang
     * @date 2019年12月21日
     * @param token
     * @param secret 秘钥,不设置则使用默认
     * @return
     */
    public static String getUsernameFromToken(String token,String secret) {
        String username = null;
        try {
            Claims claims = getClaimsFromToken(token,secret);
            if(claims != null)
                username = claims.getSubject();
        } catch (Exception e) {
        }
        return username;
    }
    
    /**
     * 从令牌获取用户ID
     * @author liliang
     * @date 2019年12月21日
     * @param token
     * @param secret 秘钥,不设置则使用默认
     * @return
     */
    public static String getUserIdFromToken(String token,String secret) {
        String userId = null;
        try {
            Claims claims = getClaimsFromToken(token,secret);
            if(claims != null)
                userId = claims.getId();
        } catch (Exception e) {
        }
        return userId;
    }
    
    /**
     * 获取令牌的过期时间
     * @author liliang
     * @date 2019年12月20日
     * @param token 令牌
     * @param secret 秘钥,不设置则使用默认
     * @return 过期时间
     */
    private static Date getExpiredDateFromToken(String token,String secret) {
        Claims claims = getClaimsFromToken(token,secret);
        return claims== null ? null : claims.getExpiration();
    }
    
    /**
     * 刷新token
     * @author liliang
     * @date 2019年12月20日
     * @param token 令牌
     * @param expiration 过期时长,单位秒(不设置则用默认12个小时)
     * @param secret 秘钥,不设置则使用默认
     * @return 新令牌
     */
    public static String refreshToken(String token,Long expiration,String secret) {
        Claims claims = getClaimsFromToken(token,secret);
        if(claims != null){
            claims.put(CREATED, new Date());
            return generateToken(claims,expiration,secret);
        }
        return null;
    }
    
    
    /**
     * 从令牌token中获取数据声明
     * @author liliang
     * @date 2019年12月20日
     * @param token 令牌
     * @param secret 秘钥,不设置则使用默认
     * @return 数据声明
     */
    public static Claims getClaimsFromToken(String token,String secret) {
        Claims claims;
        try {
            secret = StrUtil.isNotBlank(secret) ? secret :  DEFAULT_SECRET;
            claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        } catch (Exception e) {
            claims = null;
        }
        return claims;
    }
}
